package tech.darkblue.rpcui.menu.run;

import java.beans.PropertyDescriptor;
import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Observable;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.Unmarshaller;

import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.dialogs.ProgressMonitorDialog;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Dialog;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeItem;
import org.springframework.util.CollectionUtils;

import com.alibaba.fastjson.JSON;

import tech.darkblue.rpcui.attribute.ConfigAttribute;
import tech.darkblue.rpcui.attribute.FieldAttribute;
import tech.darkblue.rpcui.attribute.MethodAttribute;
import tech.darkblue.rpcui.attribute.ParameterAttribute;
import tech.darkblue.rpcui.config.Configurations;
import tech.darkblue.rpcui.extend.ConfigObserver;
import tech.darkblue.rpcui.extend.RunProcessThread;
import tech.darkblue.rpcui.extend.XmlSourceViewer;
import tech.darkblue.rpcui.util.ClassUtils;
import tech.darkblue.rpcui.util.ServiceUtils;
import tech.darkblue.rpcui.util.SwtUtils;
import tech.darkblue.rpcui.util.XmlUtils;
import tech.darkblue.rpcui.util.handler.ContextHandler;
import tech.darkblue.rpcui.util.handler.XmlHandler;

public class RunConfigDialog extends Dialog implements ConfigObserver {

    private final int maxRecursiveTimes = Integer.valueOf(Configurations.getCurrentProperty()
            .getProperty("app.recursiveTimes"));

    protected Object result;
    protected Shell shellRunConfig;
    private Text txtName;
    private StyledText txtInput;
    private StyledText txtOutput;

    private boolean isCreatedVisible;
    private Button btnApply;
    private Button btnRun;
    private Button btnRemove;
    private Tree treeParameter;

    private boolean hasRemoveItem = false;
    private ParameterAttribute parameterAttribute;
    private Document paramAttributeXmlDoc;

    /**
     * Create the dialog.
     * @param parent
     * @param style
     */
    public RunConfigDialog(Shell parent, int style) {
        this(parent, style, true);
    }

    public RunConfigDialog(Shell parent, int style, boolean isCreatedVisible) {
        super(parent, style | SwtUtils.DIALOG_RESIZE);
        this.isCreatedVisible = isCreatedVisible;
        Configurations.CONFIG_OBSERVER.addObserver(this);
    }

    /**
     * Open the dialog.
     * @return the result
     * @throws Exception
     */
    public Object open() throws Exception {
        createContents();
        SwtUtils.centerDialog(shellRunConfig, getParent());
        initParameterAttribute();
        initParameterConfiguration();
        shellRunConfig.open();
        shellRunConfig.layout();
        Display display = getParent().getDisplay();
        while (!shellRunConfig.isDisposed()) {
            if (!display.readAndDispatch()) {
                display.sleep();
            }
        }
        return result;
    }

    /**
     * Create contents of the dialog.
     */
    private void createContents() {
        shellRunConfig = new Shell(getParent(), getStyle());
        shellRunConfig.setImages(Configurations.DEFAULT_ICON_IMAGES);
        shellRunConfig.addDisposeListener(new DisposeListener() {
            public void widgetDisposed(DisposeEvent arg0) {
                Configurations.CONFIG_OBSERVER.deleteObserver(RunConfigDialog.this);
            }
        });
        shellRunConfig.setSize(700, 500);
        shellRunConfig.setText("Run Configurations");
        shellRunConfig.setLayout(new GridLayout(1, false));

        Composite composite = new Composite(shellRunConfig, SWT.NONE);
        composite.setLayout(new FillLayout(SWT.HORIZONTAL));
        GridData gd_composite = new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1);
        gd_composite.heightHint = 109;
        composite.setLayoutData(gd_composite);

        SashForm sashForm = new SashForm(composite, SWT.NONE);

        Composite compParameterTree = new Composite(sashForm, SWT.BORDER);
        compParameterTree.setLayout(new GridLayout(1, false));

        Composite composite_2 = new Composite(compParameterTree, SWT.NONE);
        composite_2.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, true, false, 1, 1));

        Button btnExpand = new Button(composite_2, SWT.NONE);
        btnExpand.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                SwtUtils.expandAllTreeItems(treeParameter.getItems(), true);
            }
        });
        btnExpand.setBounds(0, 0, 20, 20);
        btnExpand.setText("＋");

        Button btnCollapse = new Button(composite_2, SWT.NONE);
        btnCollapse.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                SwtUtils.expandAllTreeItems(treeParameter.getItems(), false);
            }
        });
        btnCollapse.setBounds(24, 0, 20, 20);
        btnCollapse.setText("－");

        btnRemove = new Button(composite_2, SWT.NONE);
        btnRemove.setEnabled(false);
        btnRemove.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                if (treeParameter.getSelectionCount() > 0) {
                    boolean isOK = MessageDialog.openConfirm(shellRunConfig, "Confirm Remove",
                            "Are you sure you want to remove selected nodes?");
                    if (isOK) {
                        TreeItem selectedItem = treeParameter.getSelection()[0];
                        if (treeParameter.indexOf(selectedItem) >= 0) {
                            List<?> selectedNodes = paramAttributeXmlDoc
                                    .selectNodes("//parameter/jarname[text()=\"" + selectedItem.getText() + "\"]");
                            if (!CollectionUtils.isEmpty(selectedNodes)) {
                                for (int i = 0; i < selectedNodes.size(); i++) {
                                    Element selectedNode = (Element) selectedNodes.get(i);
                                    paramAttributeXmlDoc.getRootElement().remove(selectedNode.getParent());
                                }
                            }
                        } else {
                            List<?> selectedNodes = paramAttributeXmlDoc
                                    .selectNodes("//parameter[@name=\"" + selectedItem.getText() + "\"]");
                            if (!CollectionUtils.isEmpty(selectedNodes)) {
                                for (int i = 0; i < selectedNodes.size(); i++) {
                                    Element selectedNode = (Element) selectedNodes.get(i);
                                    paramAttributeXmlDoc.getRootElement().remove(selectedNode);
                                }
                            }
                        }
                        // remove selected nodes
                        selectedItem.removeAll();
                        selectedItem.dispose();
                        hasRemoveItem = true;
                        btnRemove.setEnabled(false);
                        btnApply.setEnabled(true);
                    }
                }
            }
        });
        btnRemove.setBounds(48, 0, 20, 20);
        btnRemove.setText("×");

        treeParameter = new Tree(compParameterTree, SWT.BORDER);
        treeParameter.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                TreeItem treeItem = (TreeItem) e.item;
                if (treeItem != null) {
                    btnRemove.setEnabled(true);
                    if (treeParameter.indexOf(treeItem) == -1) {
                        try {
                            Document xmlDoc = DocumentHelper.parseText((String) treeItem.getData());
                            Element rootEl = xmlDoc.getRootElement();
                            txtName.setText(rootEl.attributeValue("name"));
                            txtName.setData(rootEl.attributeValue("name"));
                            txtInput.setText(XmlUtils.formatDocument(rootEl));
                            txtOutput.setText("");
                            if (!hasRemoveItem) {
                                btnApply.setEnabled(false);
                            }
                        } catch (DocumentException | IOException ex) {
                            ex.printStackTrace();
                        }
                    }
                } else {
                    btnRemove.setEnabled(false);
                }
            }
        });
        treeParameter.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));
        compParameterTree.setVisible(isCreatedVisible);

        Composite composite_3 = new Composite(sashForm, SWT.BORDER);
        composite_3.setLayout(new GridLayout(2, false));

        Label lblName = new Label(composite_3, SWT.NONE);
        lblName.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1));
        lblName.setText("Name :");

        txtName = new Text(composite_3, SWT.BORDER);
        txtName.addModifyListener(new ModifyListener() {
            public void modifyText(ModifyEvent arg0) {
                if (StringUtils.isEmpty(txtName.getText())) {
                    btnApply.setEnabled(false);
                    btnRun.setEnabled(false);
                } else {
                    btnApply.setEnabled(true);
                    btnRun.setEnabled(true);
                }
            }
        });
        txtName.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));

        SashForm sashForm_1 = new SashForm(composite_3, SWT.VERTICAL);
        GridData gd_sashForm_1 = new GridData(SWT.FILL, SWT.FILL, true, true, 2, 1);
        gd_sashForm_1.heightHint = 265;
        sashForm_1.setLayoutData(gd_sashForm_1);

        Composite composite_4 = new Composite(sashForm_1, SWT.BORDER);
        composite_4.setLayout(new GridLayout(1, false));

        Label lblInputParameter = new Label(composite_4, SWT.NONE);
        lblInputParameter.setText("Input Parameter :");

        Composite composite_4x = new Composite(composite_4, SWT.BORDER);
        composite_4x.setLayout(new FillLayout(SWT.HORIZONTAL));
        composite_4x.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));
        // input parameter
        XmlSourceViewer txtInputView = new XmlSourceViewer(composite_4x, SWT.MULTI | SWT.V_SCROLL | SWT.H_SCROLL);
        txtInput = txtInputView.getTextWidget();
        txtInput.addKeyListener(new KeyAdapter() {
            @Override
            public void keyPressed(KeyEvent e) {
                SwtUtils.bindTextShortcutKey(e, txtInput);
            }
        });
        txtInput.addModifyListener(new ModifyListener() {
            public void modifyText(ModifyEvent arg0) {
                btnApply.setEnabled(true);
            }
        });
        txtInput.setFont(Configurations.getCurrentFont());

        Composite composite_5 = new Composite(sashForm_1, SWT.BORDER);
        composite_5.setLayout(new GridLayout(1, false));
        composite_5.setVisible(isCreatedVisible);

        Label lblOutputResult = new Label(composite_5, SWT.NONE);
        lblOutputResult.setText("Output Result :");

        Composite composite_5x = new Composite(composite_5, SWT.BORDER);
        composite_5x.setLayout(new FillLayout(SWT.HORIZONTAL));
        composite_5x.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));
        // output result
        XmlSourceViewer txtOutputView = new XmlSourceViewer(composite_5x, SWT.MULTI | SWT.V_SCROLL | SWT.H_SCROLL | SWT.READ_ONLY);
        txtOutput = txtOutputView.getTextWidget();
        txtOutput.addKeyListener(new KeyAdapter() {
            @Override
            public void keyPressed(KeyEvent e) {
                SwtUtils.bindTextShortcutKey(e, txtOutput);
            }
        });
        txtOutput.setFont(Configurations.getCurrentFont());

        sashForm_1.setWeights(new int[] {60, 40});

        sashForm.setWeights(new int[] {30, 70});

        Composite composite_1 = new Composite(shellRunConfig, SWT.NONE);
        GridLayout gl_composite_1 = new GridLayout(1, false);
        gl_composite_1.marginWidth = 0;
        gl_composite_1.marginHeight = 0;
        composite_1.setLayout(gl_composite_1);
        composite_1.setLayoutData(new GridData(SWT.RIGHT, SWT.BOTTOM, false, false, 1, 1));

        Composite composite_6 = new Composite(composite_1, SWT.NONE);
        composite_6.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1));

        Button btnClose = new Button(composite_6, SWT.NONE);
        btnClose.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                shellRunConfig.close();
                shellRunConfig.dispose();
            }
        });
        btnClose.setBounds(172, 0, 80, 27);
        btnClose.setText("Close");

        Rectangle btnRunRect = new Rectangle(86, 0, 80, 27);
        btnRun = new Button(composite_6, SWT.NONE);
        btnRun.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                try {
                    txtOutput.setText("");
                    String parameterXml = txtInput.getText();
                    StringBuffer resultXml = new StringBuffer();
                    ProgressMonitorDialog progressDialog = new ProgressMonitorDialog(shellRunConfig);
                    progressDialog.run(true, true, new RunProcessThread() {
                        @Override
                        public String getMessage() {
                            return "Running, please wait...";
                        }
                        @Override
                        public void handleProcess(IProgressMonitor monitor) throws Exception {
                            ExecutorService service = Executors.newSingleThreadExecutor();
                            service.execute(new Runnable() {
                                @Override
                                public void run() {
                                    invokeTargetRpcService(parameterXml, resultXml);
                                }
                            });
                            service.shutdown();
                            while (true) {
                                if (service.isTerminated()) {
                                    break;
                                } else if (monitor.isCanceled()) {
                                    service.shutdownNow();
                                    throw new InterruptedException("Task has been canceled!");
                                }
                                Thread.sleep(200);
                            }
                        }
                    });
                    txtOutput.setText(resultXml.toString());
                } catch (Throwable ex) {
                    ex.printStackTrace();
                }
            }
        });
        btnRun.setEnabled(false);
        btnRun.setBounds(btnRunRect);
        btnRun.setText("Run");

        Rectangle btnApplyRect = new Rectangle(0, 0, 80, 27);
        btnApply = new Button(composite_6, SWT.NONE);
        btnApply.addSelectionListener(new SelectionAdapter() {
            @SuppressWarnings({ "unchecked", "rawtypes" })
            @Override
            public void widgetSelected(SelectionEvent e) {
                try {
                    if (!txtName.getText().equals(txtName.getData())) {
                        List<?> existNodes = paramAttributeXmlDoc.selectNodes("//parameter[@name=\"" + txtName.getText() + "\"]");
                        if (!CollectionUtils.isEmpty(existNodes)) {
                            MessageDialog.openError(shellRunConfig, "Error Information",
                                    "There is same parameter name [" + txtName.getText() + "] exists!");
                            return;
                        }
                    }

                    String oldName = (String) txtName.getData();
                    List<?> oldNodes = paramAttributeXmlDoc.selectNodes("//parameter[@name=\"" + oldName + "\"]");
                    if (!CollectionUtils.isEmpty(oldNodes)) {
                        Element parameterEl = DocumentHelper.parseText(txtInput.getText()).getRootElement();
                        if (isCreatedVisible) {
                            // check tree item
                            TreeItem apiJarItem = null;
                            String apiJarName = parameterEl.element("jarname").getText();
                            for (TreeItem treeItem : treeParameter.getItems()) {
                                if (treeItem.getText().equals(apiJarName)) {
                                    apiJarItem = treeItem;
                                    break;
                                }
                            }
                            if (apiJarItem == null) {
                                MessageDialog.openError(shellRunConfig, "Error Information",
                                        "Node [" + apiJarName + "] has removed or not existed!");
                                return;
                            }
                            // update tree item
                            for (TreeItem treeItem : apiJarItem.getItems()) {
                                if (treeItem.getText().equals(oldName)) {
                                    treeItem.setText(txtName.getText());
                                    treeItem.setData(parameterEl.asXML());
                                    break;
                                }
                            }
                        }

                        // update element
                        Element oldParameterEl = (Element) oldNodes.get(0);
                        List contentNodes = oldParameterEl.getParent().content();
                        parameterEl.addAttribute("name", txtName.getText());
                        contentNodes.set(contentNodes.indexOf(oldParameterEl), parameterEl);
                        txtInput.setText(XmlUtils.formatDocument(parameterEl));
                    }
                    XmlUtils.saveDocument(new File(Configurations.FILE_PARAMETERS), paramAttributeXmlDoc);
                    // update old name
                    txtName.setData(txtName.getText());
                    hasRemoveItem = false;
                    btnApply.setEnabled(false);
                    MessageDialog.openInformation(shellRunConfig, "Information", "Apply attributes of parameter success!");
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        });
        btnApply.setEnabled(false);
        btnApply.setBounds(btnApplyRect);
        btnApply.setText("Apply");

        if (!isCreatedVisible) {
            btnRun.setVisible(false);
            btnRun.setBounds(btnApplyRect);
            btnApply.setBounds(btnRunRect);
        }
    }

    private void initParameterAttribute() throws DocumentException {
        paramAttributeXmlDoc = XmlUtils.getDocument(new File(Configurations.FILE_PARAMETERS));
        if (isCreatedVisible) {
            List<?> apiJarTexts = paramAttributeXmlDoc.selectNodes("//parameter/jarname/text()");
            if (!CollectionUtils.isEmpty(apiJarTexts)) {
                Set<String> apiJarNames = new HashSet<String>();
                for (int i = 0; i < apiJarTexts.size(); i++) {
                    org.dom4j.Text apiJarText = (org.dom4j.Text) apiJarTexts.get(i);
                    if (apiJarNames.contains(apiJarText.getText())) {
                        continue;
                    }
                    // add jarname item
                    apiJarNames.add(apiJarText.getText());
                    TreeItem treeItem = new TreeItem(treeParameter, SWT.NONE);
                    treeItem.setText(apiJarText.getText());
                    // get parameters
                    List<?> apiJarNodes = paramAttributeXmlDoc
                            .selectNodes("//parameter/jarname[text()=\"" + apiJarText.getText() + "\"]");
                    if (!CollectionUtils.isEmpty(apiJarNodes)) {
                        for (int j = 0; j < apiJarNodes.size(); j++) {
                            Element parameter = ((Element) apiJarNodes.get(j)).getParent();
                            TreeItem subTreeItem = new TreeItem(treeItem, SWT.NONE);
                            subTreeItem.setText(parameter.attributeValue("name"));
                            subTreeItem.setData(parameter.asXML());
                        }
                    }
                }
            }
        }
    }

    private void initParameterConfiguration() throws Exception {
        if (parameterAttribute != null) {
            File targetParamXml = new File(Configurations.DIR_TEMP, parameterAttribute.getJarName() + ".param.xml");
            XmlUtils.createAndSave(targetParamXml , new XmlHandler() {
                @Override
                public void handle(Document xmlDoc) throws Exception {
                    // parameters
                    Element rootEl = DocumentHelper.createElement("parameters");
                    xmlDoc.setRootElement(rootEl);
                    // parameter
                    Element parameterEl = rootEl.addElement("parameter");
                    SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss");
                    parameterEl.addAttribute("name", parameterAttribute.getMethodName() + "_" + dateFormat.format(new Date()));
                    // jarname
                    parameterEl.addElement("jarname").setText(parameterAttribute.getJarName());
                    // interface
                    parameterEl.addElement("interface").setText(parameterAttribute.getInterfaceName());
                    // method
                    parameterEl.addElement("method").setText(parameterAttribute.getMethodName());
                    // arguments
                    String[] parameterTypes = parameterAttribute.getParameterTypes();
                    if (parameterTypes != null && parameterTypes.length > 0) {
                        // get parameter types
                        Type[] genericParameterTypes = ClassUtils.getMethodParameterTypes(parameterAttribute);
                        // add attribute
                        Element argumentsEl = parameterEl.addElement("arguments");
                        for (int i = 0; i < parameterTypes.length; i++) {
                            Element argumentEl = argumentsEl.addElement("arg" + i);
                            argumentEl.addAttribute("class", ClassUtils.toDisplayClassName(parameterTypes[i]));
                            // handle advanced type
                            handleAdvancedClassType(maxRecursiveTimes, genericParameterTypes[i], argumentEl);
                        }
                    }
                    // configuration
                    Element configuration = parameterEl.addElement("configuration");
                    configuration.addElement("group");
                    configuration.addElement("version");
                    configuration.addElement("output").setText("object");
                }
            });
            // set configuration
            Document xmlDoc = XmlUtils.getDocument(targetParamXml);
            Element parameterEl = xmlDoc.getRootElement().element("parameter");
            txtName.setText(parameterEl.attributeValue("name"));
            // save old name
            txtName.setData(parameterEl.attributeValue("name"));
            txtInput.setText(XmlUtils.formatDocument(parameterEl));
            // add to parameters
            paramAttributeXmlDoc.getRootElement().add(DocumentHelper.parseText(parameterEl.asXML()).getRootElement());
            FileUtils.forceDelete(targetParamXml);
        }
    }

    protected void invokeTargetRpcService(String parameterXml, StringBuffer resultXml) {
        resultXml.append("<output>\n");
        try {
            // invoke target method
            ClassUtils.applyAndHandleContext(new ContextHandler() {
                @Override
                public void handle() throws Exception {
                    Element rootEl = DocumentHelper.parseText(parameterXml).getRootElement();
                    String interfaceName = rootEl.element("interface").getText();
                    String methodName = rootEl.element("method").getText();
                    Element argumentsEl = rootEl.element("arguments");
                    // get attribute of method
                    MethodAttribute methodAttr = getInstanceMethodAttribute(argumentsEl);
                    // get attribute of configuration
                    String configuration = rootEl.element("configuration").asXML();
                    ConfigAttribute configAttr = null;
                    if (StringUtils.isNotBlank(configuration)) {
                        JAXBContext jaxbContext = JAXBContext.newInstance(ConfigAttribute.class);
                        Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
                        configAttr = (ConfigAttribute) unmarshaller.unmarshal(new StringReader(configuration));
                    }
                    // invoke dubbo service
                    Object result = null;
                    if (configAttr == null) {
                        result = ServiceUtils.invokeService(interfaceName, methodName, methodAttr);
                    } else  {
                        result = ServiceUtils.invokeService(interfaceName, methodName, methodAttr, configAttr);
                    }
                    // set output result
                    resultXml.append("\t<status>SUCCESS</status>\n");
                    if (result == null) {
                        resultXml.append("\t<result>\n").append(result).append("\n\t</result>\n");
                    } else {
                        if (configAttr != null && "json".equals(configAttr.getOutput())) {
                            resultXml.append("\t<result>\n").append(JSON.toJSONString(result)).append("\n\t</result>\n");
                        } else {
                            resultXml.append("\t<result>\n").append(result.toString()).append("\n\t</result>\n");
                        }
                    }
                }
            });
        } catch (Throwable t) {
            resultXml.append("\t<status>FAIL</status>\n");
            resultXml.append("\t<result>\n").append(ExceptionUtils.getStackTrace(t)).append("\n\t</result>\n");
            t.printStackTrace();
        }

        resultXml.append("</output>");
    }

    private MethodAttribute getInstanceMethodAttribute(Element argumentsEl) throws Exception {
        MethodAttribute attribute = null;
        if (argumentsEl != null) {
            List<?> argumentEls = argumentsEl.elements();
            if (!CollectionUtils.isEmpty(argumentEls)) {
                Object[] parameters = new Object[argumentEls.size()];
                Class<?>[] parameterTypes = new Class<?>[argumentEls.size()];
                // create parameter object
                for (int i = 0; i < argumentEls.size(); i++) {
                    Element argumentEl = (Element) argumentEls.get(i);
                    String parameterTypeName = argumentEl.attributeValue("class");
                    Object parameter = null;
                    Class<?> parameterType = null;
                    // get class of parameter
                    if (ClassUtils.isPrimitiveClassType(parameterTypeName)) {
                        parameterType = ClassUtils.getPrimitiveClassType(parameterTypeName);
                        parameter = getParameterInstance(parameterType, argumentEl);
                    } else {
                        parameterType = Class.forName(ClassUtils.toJavaClassName(parameterTypeName));
                        // get attributes of fields
                        Set<FieldAttribute> fieldAttrs = ClassUtils.getFieldAttributes(parameterTypeName);
                        if (CollectionUtils.isEmpty(fieldAttrs)) {
                            // get instance of parameter
                            parameter = getParameterInstance(parameterType, argumentEl);
                        } else {
                            // get instance of bean
                            parameter = getBeanParameterInstance(parameterType, argumentEl);
                        }
                    }
                    parameterTypes[i] = parameterType;
                    parameters[i] = parameter;
                }

                // set method attribute
                attribute = new MethodAttribute();
                attribute.setParameters(parameters);
                attribute.setParameterTypes(parameterTypes);
            }
        }

        return attribute;
    }

    private void handleAdvancedClassType(int recursiveTimes, Type valueType, Element nodeEl) throws Exception {
        if (recursiveTimes <= 0) {
            return;
        }
        if (Date.class.equals(valueType)) {
            // Date
            nodeEl.addElement("value").addAttribute("format", "yyyy/MM/dd HH:mm:ss");
        } else if (valueType instanceof ParameterizedType) {
            // Collection and Map
            ParameterizedType parameteredType = (ParameterizedType) valueType;
            Class<?> valueTypeClass = (Class<?>) parameteredType.getRawType();
            Type[] argumentTypes = parameteredType.getActualTypeArguments();

            // check and add sub attributes
            if (Collection.class.isAssignableFrom(valueTypeClass)) {
                // Collection
                Element valueEl = nodeEl.addElement("value");
                valueEl.addAttribute("class", ClassUtils.getActualTypeClassName(argumentTypes[0]));
                handleAdvancedClassType(recursiveTimes - 1, argumentTypes[0], valueEl);
            } else if (Map.class.isAssignableFrom(valueTypeClass)) {
                // Map
                Element entryEl = nodeEl.addElement("entry");
                // key type
                Element keyEl = entryEl.addElement("key");
                keyEl.addAttribute("class", ClassUtils.getActualTypeClassName(argumentTypes[0]));
                handleAdvancedClassType(recursiveTimes - 1, argumentTypes[0], keyEl);
                // value type
                Element valueEl = entryEl.addElement("value");
                valueEl.addAttribute("class", ClassUtils.getActualTypeClassName(argumentTypes[1]));
                handleAdvancedClassType(recursiveTimes - 1, argumentTypes[1], valueEl);
            } else {
                // Others
                handleAdvancedClassType(recursiveTimes - 1, valueTypeClass, nodeEl);
            }
        } else if (valueType instanceof GenericArrayType) {
            // GenericArrayType
            Type arrayType = ((GenericArrayType) valueType).getGenericComponentType();
            Element valueEl = nodeEl.addElement("value");
            valueEl.addAttribute("class", ClassUtils.getActualTypeClassName(arrayType));
            handleAdvancedClassType(recursiveTimes - 1, arrayType, valueEl);
        } else if (valueType instanceof Class) {
            // Class
            Class<?> valueTypeClass = (Class<?>) valueType;
            if (ClassUtils.isPrimitiveClassType(valueTypeClass.getName())) {
                // ignore primitive type
                return;
            } else if (valueTypeClass.isArray()) {
                // Array
                Class<?> componentType = valueTypeClass.getComponentType();
                Element valueEl = nodeEl.addElement("value");
                valueEl.addAttribute("class", componentType.getName());
                handleAdvancedClassType(recursiveTimes - 1, componentType, valueEl);
            } else {
                // Bean
                Set<FieldAttribute> fieldAttrs = ClassUtils.getFieldAttributes(valueTypeClass.getName());
                if (!CollectionUtils.isEmpty(fieldAttrs)) {
                    // constructor node
                    nodeEl.addElement("constructor-args");
                    // field nodes type
                    for (FieldAttribute fieldAttr : fieldAttrs) {
                        Element FieldEl = nodeEl.addElement(fieldAttr.getName());
                        FieldEl.addAttribute("class",
                                ClassUtils.toDisplayClassName(ClassUtils.getActualTypeClassName(fieldAttr.getType())));
                        handleAdvancedClassType(recursiveTimes - 1, fieldAttr.getType(), FieldEl);
                    }
                }
            }
        }
    }

    private Object getParameterInstance(Class<?> parameterType, Element argumentEl) throws Exception {
        Object parameter = null;
        if (StringUtils.isNotEmpty(argumentEl.getText())) {
            if (ClassUtils.isPrimitiveClassType(parameterType.getName())) {
                parameterType = ClassUtils.getAdvancedClassType(parameterType.getName());
            }
            if (parameterType.isEnum()) {
                Object[] enums = parameterType.getEnumConstants();
                for (Object object : enums) {
                    if (parameterType.getMethod("name").invoke(object).equals(argumentEl.getText())) {
                        parameter = object;
                        break;
                    }
                }
            } else if (parameterType.isArray()) {
                // Array
                if (StringUtils.isNotEmpty(argumentEl.elementText("value"))) {
                    // set value
                    List<?> valueEls = argumentEl.elements("value");
                    if (!CollectionUtils.isEmpty(valueEls)) {
                        parameter = Array.newInstance(parameterType.getComponentType(), valueEls.size());
                        for (int i = 0; i < valueEls.size(); i++) {
                            Element valueEl = (Element) valueEls.get(i);
                            Class<?> parameterTypeClass = null;
                            String parameterTypeName = ClassUtils.toJavaClassName(valueEl.attributeValue("class"));
                            if (ClassUtils.isPrimitiveClassType(parameterTypeName)) {
                                parameterTypeClass = ClassUtils.getPrimitiveClassType(parameterTypeName);
                            } else {
                                parameterTypeClass = Class.forName(parameterTypeName);
                            }
                            Object value = getParameterInstance(parameterTypeClass, valueEl);
                            Array.set(parameter, i, value);
                        }
                    }
                }
            } else if (Collection.class.isAssignableFrom(parameterType)) {
                // Collection
                if (StringUtils.isNotEmpty(argumentEl.elementText("value"))) {
                    if (parameterType.equals(Collection.class) || parameterType.equals(List.class)) {
                        parameter = new ArrayList<Object>();
                    } else if (parameterType.equals(Set.class)) {
                        parameter = new HashSet<Object>();
                    } else {
                        parameter = parameterType.newInstance();
                    }
                    // set value
                    List<?> valueEls = argumentEl.elements("value");
                    if (!CollectionUtils.isEmpty(valueEls)) {
                        Method addMethod = parameterType.getMethod("add", Object.class);
                        for (int i = 0; i < valueEls.size(); i++) {
                            Element valueEl = (Element) valueEls.get(i);
                            Object value = getParameterInstance(Class.forName(ClassUtils.toJavaClassName(
                                    valueEl.attributeValue("class"))), valueEl);
                            addMethod.invoke(parameter, value);
                        }
                    }
                }
            } else if (Map.class.isAssignableFrom(parameterType)) {
                // Map
                if (StringUtils.isNotEmpty(argumentEl.element("entry").elementText("key"))) {
                    if (parameterType.equals(Map.class)) {
                        parameter = new HashMap<Object, Object>();
                    } else {
                        parameter = parameterType.newInstance();
                    }
                    // set value
                    List<?> entryEls = argumentEl.elements("entry");
                    if (!CollectionUtils.isEmpty(entryEls)) {
                        Method putMethod = parameterType.getMethod("put", Object.class, Object.class);
                        for (int i = 0; i < entryEls.size(); i++) {
                            Element entryEl = (Element) entryEls.get(i);
                            // key
                            Element keyEl = entryEl.element("key");
                            Object keyObject = getParameterInstance(Class.forName(ClassUtils.toJavaClassName(
                                    keyEl.attributeValue("class"))), keyEl);
                            // value
                            Element valueEl = entryEl.element("value");
                            Object valueObject = getParameterInstance(Class.forName(ClassUtils.toJavaClassName(
                                    valueEl.attributeValue("class"))), valueEl);
                            putMethod.invoke(parameter, keyObject, valueObject);
                        }
                    }
                }
            } else if ("\"\"".equals(argumentEl.getText())) {
                // deal with ""
                if (String.class.equals(parameterType)) {
                    Constructor<?> constructor = parameterType.getConstructor(String.class);
                    parameter = constructor.newInstance("");
                } else {
                    parameter = parameterType.newInstance();
                }
            } else if (Date.class.equals(parameterType)) {
                // Date
                if (StringUtils.isNotEmpty(argumentEl.elementText("value"))) {
                    String dateType = argumentEl.element("value").attributeValue("format");
                    if (StringUtils.isEmpty(dateType)) {
                        throw new IllegalArgumentException("Attribute [format] of value node [" + argumentEl.getName() +
                                "/value] is not found");
                    } else {
                        SimpleDateFormat dateFormat = new SimpleDateFormat(dateType);
                        parameter = dateFormat.parse(argumentEl.elementText("value"));
                    }
                }
            } else {
                // Others
                Set<FieldAttribute> fieldAttrs = ClassUtils.getFieldAttributes(parameterType.getName());
                if (CollectionUtils.isEmpty(fieldAttrs)) {
                    Constructor<?> constructor = parameterType.getConstructor(String.class);
                    parameter = constructor.newInstance(argumentEl.getText());
                } else {
                    parameter = getBeanParameterInstance(parameterType, argumentEl);
                }
            }
        }

        return parameter;
    }

    private Object getBeanParameterInstance(Class<?> parameterType, Element argumentEl) throws Exception {
        Object parameter = null;
        List<?> fieldEls = argumentEl.elements();
        if (!CollectionUtils.isEmpty(fieldEls)) {
            Element constructorEl = argumentEl.element("constructor-args");
            // create instance
            MethodAttribute attribute = getInstanceMethodAttribute(constructorEl);
            if (attribute == null) {
                parameter = parameterType.newInstance();
            } else {
                Constructor<?> constructor = parameterType.getConstructor(attribute.getParameterTypes());
                parameter = constructor.newInstance(attribute.getParameters());
            }
            // set value to fields
            for (int i = 0; i < fieldEls.size(); i++) {
                // get field element
                Element fieldEl = (Element) fieldEls.get(i);
                // filter constructor arguments
                if ("constructor-args".equals(fieldEl.getName())) {
                    continue;
                } else if (StringUtils.isNotEmpty(fieldEl.getText())) {
                    Class<?> fieldType = null;
                    String parameterTypeName = ClassUtils.toJavaClassName(fieldEl.attributeValue("class"));
                    if (ClassUtils.isPrimitiveClassType(parameterTypeName)) {
                        fieldType = ClassUtils.getPrimitiveClassType(parameterTypeName);
                    } else {
                        fieldType = Class.forName(parameterTypeName);
                    }
                    Method setterMethod = null;
                    if (StringUtils.isNotEmpty(fieldEl.attributeValue("setter"))) {
                        setterMethod = parameterType.getMethod(fieldEl.attributeValue("setter"), fieldType);
                    } else {
                        PropertyDescriptor propDesc = new PropertyDescriptor(fieldEl.getName(), parameterType);
                        setterMethod = propDesc.getWriteMethod();
                    }
                    Object fieldValue = getParameterInstance(fieldType, fieldEl);
                    // set field value
                    setterMethod.invoke(parameter, fieldValue);
                }
            }
        }

        return parameter;
    }

    public void setParameterAttribute(ParameterAttribute parameterAttribute) {
        this.parameterAttribute = parameterAttribute;
    }

    @Override
    public void update(Observable o, Object arg) {
        Font currentFont = Configurations.getCurrentFont();
        txtInput.setFont(currentFont);
        txtOutput.setFont(currentFont);
    }

}
